/*!
 * Node-event module v1.0.0 of jRaiser javascript library
 * http://code.google.com/p/jraiser/
 *
 * Author: Heero.Luo
 * Licensed under MIT license
 *
 * Date: Wed Jun 27 2012 21:57:18 GMT+0800
 */
define(function(require, exports, module) { 'use strict';

var core = require('core/1.0.x/core'), $base = require('node-base/1.0.x/node-base');


// 默认情况下会冒泡的事件
var bubbleEvents = {
	scroll : 1, resize : 1, select : 1, error : 1,
	reset : 1, submit : 1, change : 1, abort : 1
};
var eventTypes = { };
'keydown keypress keyup'.split(' ').forEach(function(e) {
	eventTypes[e] = 'key';
	// 按键事件默认冒泡
	bubbleEvents[e] = 1;
});
'mousedown mouseup mousemove mouseover mouseout click dblclick'.split(' ').forEach(function(e) {
	eventTypes[e] = 'mouse';
	// 鼠标事件默认冒泡
	bubbleEvents[e] = 1;
});

// 检查节点是否支持事件操作
function isSupportEvent(node) {
	var nodeType = node.nodeType;
	return $base.isWindow(node) || ( !$base.isXMLNode(node) && (nodeType === 1 || nodeType === 9) );
}

// 解析事件类型参数为数组
var explainEventType = $base.splitBySpace;


/**
 * 事件监听，部分代码从jQuery 1.7.1修改而来
 * !!注意：
 *   无法移除非本模块添加的回调函数；
 *   由于调用本模块之前页面可能已经加载完，故window的onload事件可能不会执行
 */
// 事件对象都会有的属性
var EVENT_PUB_PROPS = 'type altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which'.split(' ');
// 事件属性兼容性修复 (from jQuery.event.fixHooks)
var fixHooks = {
	// 按键事件对象修复
	key : {
		props : 'char charCode key keyCode'.split(' '),
		filter: function(event, original) {
			if (event.which == null) {
				event.which = original.charCode != null ? original.charCode : original.keyCode;
			}
			
			return event;
		}
	},
	// 鼠标事件对象修复
	mouse : {
		props: 'button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY'.split(' '),
		filter: function(event, original) {
			var eventDoc, doc, body,
				button = original.button,
				fromElement = original.fromElement;

			if (event.pageX == null && original.clientX != null) {
				eventDoc = event.target.ownerDocument || document;
				doc = eventDoc.documentElement;
				body = eventDoc.body;

				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
			}

			if (!event.relatedTarget && fromElement) {
				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
			}

			// 1 === left; 2 === middle; 3 === right
			if (!event.which && !core.isUndefined(button)) {
				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
			}

			return event;
		}
	}
};

function returnFalse() { return false; }
function returnTrue() { return true; }

// 事件参数类
var EventArg = core.newClass(function(src) {
	var t = this;

	t.timeStamp = src.timeStamp || +new Date;
	
	// 记录原事件对象
	t.originalEvent = src;
	
	t.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
	
	// 修复属性
	var props = EVENT_PUB_PROPS, fixHook = fixHooks[ eventTypes[src.type] ];
	if (fixHook && fixHook.props) {
		props = props.concat(fixHook.props);
	}
	props.forEach(function(p) {
		if (p in src) { t[p] = src[p]; }
	});
	
	if (!t.target) { t.target = src.srcElement || document; }
	if (t.target.nodeType === 3) { t.target = t.target.parentNode; }

	if (t.metaKey === undefined) { t.metaKey = t.ctrlKey; }
	
	if (fixHook && fixHook.filter) {
		fixHook.filter(t, src);
	}
}, {
	// 禁止冒泡
	stopPropagation : function() {
		this.isPropagationStopped = returnTrue;

		var e = this.originalEvent;
		if (e.stopPropagation) {
			e.stopPropagation();
		} else {
			e.cancelBubble = true;
		}
	},

	// 阻止默认行为
	preventDefault : function() {
		this.isDefaultPrevented = returnTrue;
		
		var e = this.originalEvent;
		if (e.preventDefault) {
			e.preventDefault();
		} else {
			e.returnValue = false;
		}
	},
	
	isDefaultPrevented : returnFalse,
	isPropagationStopped : returnFalse
});

// 根据特征判断初始化不同浏览器下的事件监听函数
var addEvent, removeEvent;
if (document.addEventListener) {
	addEvent = function(e, n, h) { e.addEventListener(n, h, false); };
	removeEvent = function(e, n, h) { e.removeEventListener(n, h, false); };
} else if (document.attachEvent) {
	addEvent = function(e, n, h) { e.attachEvent('on' + n, h); };
	removeEvent = function(e, n, h) { e.detachEvent('on' + n, h); };
}

// 事件编号
var EVENT_ID_SPACE = '_JRAISER_EVENT_ID_', autoId = 0;

// 存放回调函数的对象
var eventSpace = { };

function createCallback(node, space, type, fn, data) {
	// 生成不重复的事件编号，以便在移除callback时找到真正的callback
	var eventId = fn[EVENT_ID_SPACE] = fn[EVENT_ID_SPACE] || ++autoId;
	// 生成特定事件空间
	space = space[type] = space[type] || { };
	
	var callback = space[eventId];
	if (!callback) {
		// 这个才是要添加的callback
		callback = function(e) {
			e = new EventArg(e);
			e.data = data;
			var result = fn.call(node, e);
			false === result && e.preventDefault();
			true === e.cancelBubble && e.stopPropagation();
			return result;
		};
		
		space[eventId] = callback;
	}
	
	addEvent(node, type, callback);
}
function disposeCallback(node, space, type, eventId) {
	if (!space) { return; }
	
	var i;
	if (eventId) {	// 只移除一个callback
		space = space[type];
		if (space && space[eventId]) {
			removeEvent(node, type, space[eventId]);
			delete space[eventId];
		}
	} else if (type) {	// 移除某种事件的所有callback
		var temp = space[type];
		if (temp) {
			for (i in temp) {
				disposeCallback(node, space, type, i);
			}
		}
	} else {	// 移除所有事件的所有callback
		for (i in space) {
			disposeCallback(node, space, i);
		}
	}
}

/**
 * 在指定节点上注册事件监听
 * @param {DOMElement} 节点
 * @param {String|Array<String>} 事件类型。多个事件类型用空格隔开，或者以数组传入
 * @param {Function} 回调函数
 * @param {Mixed} 额外传入的数据，可通过事件对象的data属性获得
 * @param {Boolean} 此参数仅用于在循环调用中忽略对事件类型的重复解析
 */
function on(node, eventTypes, fn, data, isPass) {
	if (!isSupportEvent(node)) { return; }
	
	if (!isPass) { eventTypes = explainEventType(eventTypes); }

	var nodeId = $base.uniqueId(node), space = eventSpace[nodeId] = eventSpace[nodeId] || { };
	
	eventTypes.forEach(function(type) {
		createCallback(node, space, type, fn, data);
	});
}
/**
 * 在指定节点上移除事件监听
 * @param {DOMElement} 节点
 * @param {String|Array<String>} 事件类型，如果为空，则移除所有事件类型的监听。
 *                               多个事件类型用空格隔开，或者以数组传入
 * @param {Function} 回调函数，如果为空，则移除指定事件类型的所有监听
 * @param {Boolean} 此参数仅用于在循环调用中忽略对事件类型的重复解析
 */
function off(node, eventTypes, fn, isPass) {
	if (!isSupportEvent(node)) { return; }
	
	if (!isPass) { eventTypes = explainEventType(eventTypes); }
	
	var space = eventSpace[$base.uniqueId(node)], eventId;
	if (fn) {
		eventId = fn[EVENT_ID_SPACE];
		// 无法移除没有事件编号的callback
		if (!eventId) { return; }
	}
	
	if (eventTypes) {
		eventTypes.forEach(function(type) {
			disposeCallback(node, space, type, eventId);
		});
	} else {
		disposeCallback(node, space);
	}
}


/**
 * 模拟事件触发，从YUI 3.4.1的event-simulate模块修改而来
 */
// 根据特征判断初始化不同浏览器下的事件触发函数
var eventSimulators = { };
if (document.createEvent) {
	eventSimulators.key = function(target, o) {
		var doc = document, e;
		try {
			e = doc.createEvent('KeyEvents');
			e.initKeyEvent(o.type, o.bubbles, o.cancelable, o.view,
				o.ctrlKey, o.altKey, o.shiftKey, o.metaKey, o.keyCode, o.charCode);
		} catch(ex) {
			try {
				e = doc.createEvent('Events');
			} catch (ex){
				e = doc.createEvent('UIEvents');
			} finally {
				e.initEvent(o.type, o.bubbles, o.cancelable);

				core.mix(e, o, {
					whiteList : ['view', 'altKey', 'ctrlKey', 'shiftKey', 'metaKey', 'keyCode', 'charCode']
				});
			}
		}
		
		target.dispatchEvent(e);
	};
	
	eventSimulators.mouse = function(target, o) {
		var doc = document, e = doc.createEvent('MouseEvent');
		if (e.initMouseEvent) {
			e.initMouseEvent(
				o.type, o.bubbles, o.cancelable, o.view, o.detail,
				o.screenX, o.screenY, o.clientX, o.clientY,
				o.ctrlKey, o.altKey, o.shiftKey, o.metaKey,
				o.button, o.relatedTarget
			);
		} else {
			e = doc.createEvent('UIEvents');
            e.initEvent(o.type, o.bubbles, o.cancelable);
            core.mix(e, o, {
				whiteList : [
					'view', 'detail', 'screenX', 'screenY', 'clientX', 'clientY',
					'ctrlKey', 'altKey', 'metaKey', 'shiftKey', 'button', 'relatedTarget'
				]
			});
		}
		
		if (o.relatedTarget && !e.relatedTarget){
            if (o.type === 'mouseout'){
                e.toElement = o.relatedTarget;
            } else if (o.type === 'mouseover'){
                e.fromElement = o.relatedTarget;
            }
        }

        target.dispatchEvent(e);
	};
	
	eventSimulators.ui = function(target, o) {
		var e = doc.createEvent('UIEvents');
        e.initUIEvent(o.type, o.bubbles, o.cancelable, o.view, o.detail);

        target.dispatchEvent(e);
	};
} else if (document.createEventObject) {
	eventSimulators.key = function(target, o) {
		var e = document.createEventObject();
		
		core.mix(e, o, {
			whiteList : ['bubbles', 'cancelable', 'view', 'ctrlKey', 'altKey', 'shiftKey', 'metaKey']
		});
        e.keyCode = o.charCode > 0 ? o.charCode : o.keyCode;

        target.fireEvent('on' + o.type, e);
	};
	
	eventSimulators.mouse = function(target, o) {
		var e = document.createEventObject();
		
		core.mix(e, o, {
			whiteList : [
				'bubbles', 'cancelable', 'view', 'detail', 'screenX', 'screenY',
				'clientX', 'clientY', 'ctrlKey', 'altKey', 'shiftKey', 'metaKey'
			]
		});
		switch (o.button) {
			case 0:
                e.button = 1;
                break;
            case 1:
                e.button = 4;
                break;
            default:
                e.button = 0;
		}
		e.relatedTarget = o.relatedTarget;
		
		target.fireEvent('on' + o.type, e);
	};
	
	eventSimulators.ui = function(target, o) {
		var e = document.createEventObject();
		core.mix(e, o, {
			whiteList : ['bubbles', 'cancelable', 'view', 'detail']
		});

        target.fireEvent('on' + o.type, e);
	};
}

// 各种事件下的默认参数
var setDefaultEventOptions = {
	key : function(o) {
		return core.mix(o, {
			bubbles : true, cancelable : true, view : window,
			ctrlKey : false, altKey : false,
			shiftKey : false, metaKey : false,
			keyCode : 0, charCode : 0
		});
	},
	
	mouse : function(o) {
		return core.mix(o, {
			bubbles : true, cancelable : o.type != 'mousemove', view : window, detail : 1,
			screenX : 0, screenY : 0, clientX : 0, clientY : 0,
			ctrlKey : false, altKey : false, shiftKey : false, metaKey : false,
			button : 0, relatedTarget : null
		});
	},
	
	ui : function(o) {
		return core.mix(o, {
			bubbles : o.type in bubbleEvents,
			cancelable : o.type === 'submit',
			view : window,
			detail : 1
		});
	}
};

/**
 * 模拟事件触发
 * @param {DOMElement} DOM节点
 * @param {String} 事件类型
 * @param {Object} 事件参数设置，如果为空，则使用默认设置
 */
function trigger(target, type, o) {
	if ( !isSupportEvent(target) ) { return; }
	
	var simType = eventTypes[type] || 'ui';
	o = o || { };
	o.type = type;
	eventSimulators[simType]( target, setDefaultEventOptions[simType](o) );
}


// 接口输出
core.extend(exports, {
	/**
	 * See line 218
	 */
	on : on,
	
	/**
	 * See line 237
	 */
	off : off,
	
	/**
	 * See line 409
	 */
	trigger : trigger,
	
	/**
	 * 事件参数类
	 */
	EventArg : EventArg,
	
	/**
	 * node模块的扩展接口
	 */
	nodeApis : {
		/**
		 * 在当前所有节点上注册事件监听
		 * @param {DOMElement} 节点
		 * @param {String,Array<String>} 事件类型。多个事件类型用空格隔开，或者以数组传入
		 * @param {Function} 回调函数
		 * @param {Mixed} 额外传入的数据，可通过事件对象的data属性获得
		 * @return {NodeList} 当前节点集合
		 */
		on : function(types, fn, data) {
			types = explainEventType(types);
			if (types) {
				this.forEach(function(node) {
					on(node, types, fn, data, true);
				});
			}

			return this;
		},
		
		/**
		 * 在当前所有节点上移除事件监听
		 * @param {String,Array<String>} 事件类型，如果为空，则移除所有事件类型的监听。
		 *                               多个事件类型用空格隔开，或者以数组传入
		 * @param {Function} 回调函数，如果为空，则移除指定事件类型的所有监听
		 * @return {NodeList} 当前节点集合
		 */
		off : function(types, fn) {
			types = explainEventType(types);
			this.forEach(function(node) {
				off(node, types, fn, true);
			});

			return this;
		},
		
		/**
		 * 模拟当前所有节点的事件触发
		 * @param {String} 事件类型
		 * @param {Object} 事件参数设置
		 * @return {NodeList} 当前节点集合
		 */
		trigger : function(type, o){
			this.forEach(function(node) {
				trigger(node, type, o);
			});

			return this;
		}
	}
});

// 添加事件快捷访问
'blur focus focusin focusout load resize scroll unload \
click dblclick mousedown mouseup mousemove mouseover mouseout \
change select submit reset keydown keypress keyup error contextmenu'.split(' ').forEach(function(type) {
	exports.nodeApis[type] = function(fn, data) {
		return arguments.length ? this.on(type, fn, data) : this.trigger(type);
	};
});

});